package com.huiyeji.hui.ws;

import com.huiyeji.hui.auth.Authenticator;
import com.huiyeji.hui.auth.License;
import com.huiyeji.hui.config.WebSocketConfig;
import com.huiyeji.hui.entity.User;
import com.huiyeji.hui.exception.AuthException;
import com.huiyeji.hui.service.ApplicationService;
import com.huiyeji.hui.utils.MessageUtil;
import com.huiyeji.hui.vo.MessageVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author zhaoduo
 * 每当一个用户建立连接后,系统就会 new 一个ChatEndpoint对象,
 * 可以把ChatEndpoint对象当成一个客户端,发消息和收消息都是靠这个客户端对象,放入在线用户容器中
 */
@ServerEndpoint(value = "/chat/channel",configurator = WebSocketConfig.class,encoders = {WebSocketConfig.MessageVoEncoder.class})
@Component
public class ChatEndpoint {

    //在线用户容器(必须有这个),key是用户id,value是
    public static final ConcurrentHashMap<Long,ChatEndpoint> ONLINE_USERS = new ConcurrentHashMap<>();

    //在线用户数量(可有可无)
    public static final AtomicInteger ONLINE_USER_COUNT = new AtomicInteger(0);

    //准备注入service,注意必须是静态变量
    private static ApplicationService service;

    //只能用setter注入!
    @Autowired
    public void setService(ApplicationService service) {
        ChatEndpoint.service = service;
    }

    private HttpSession httpSession;

    //真正用来发消息的对象
    private Session session;

    private Long uid;

    //认证用的,可有可无.自己写的
    private static final Authenticator AUTHENTICATOR = Authenticator.getInstance();

    /*
     *当用户登陆后连接到服务器,触发带有@OnOpen注解的方法
     */
    @OnOpen
    public void doOpen(Session session, EndpointConfig config) throws IOException, EncodeException {
        //取httpsession,取它的目的就是为了拿到该登录用户的id
        HttpSession httpSession = (HttpSession) config.getUserProperties().get("httpSession");
        try{
            User user = AUTHENTICATOR.check(httpSession, License.GENERAL);
            this.httpSession = httpSession;
            this.session = session;
            this.uid =  user.getUid();
            ONLINE_USER_COUNT.addAndGet(1);
            ONLINE_USERS.put(uid,this);
        }catch (AuthException e){
            session.getBasicRemote().sendObject(MessageUtil.buildErrorMessageVo("未登录,连接失败"));
        }

        //上线通知好友
        MessageVo vo = new MessageVo(
                uid,
                null,
                null,
                LocalDateTime.now(),
                MessageVo.Type.ONLINE_NOTICE
        );
        service.noticeFriends(vo);
    }

    @OnError
    public void doError(Session session,Throwable throwable) throws IOException, EncodeException {
        MessageVo vo = new MessageVo();
        vo.setType(MessageVo.Type.ERROR);
        vo.setContent(throwable.getMessage());
        vo.setSender(uid);
        vo.setReceiver(uid);
        session.getBasicRemote().sendObject(vo);
    }

    /*
     * 重点:当服务器收到来自客户端的消息时,都会触发这个方法,message参数就是来自客户端的消息
     */
    @OnMessage
    public void doReceive(Session session,String message) throws EncodeException, IOException {
        MessageVo vo = MessageUtil.buildMessageVo(message);//把消息封装成vo,只要客户端服务端约定好消息格式,就没问题,这个vo里有收信人的id
        vo.setSender(uid);//设置发送人
        vo.getType().send(service,vo);//发消息给收信人,具体过程进入service可查看,发完消息后会重新构建vo
        send(vo);//服务器把新的vo发给发消息的人,这一步其实可有可无,看系统怎么约定了
    }

    /*
     * 一旦关闭连接则会调用这个方法
     */
    @OnClose
    public void doClose(Session session){

        //销毁session
        if (httpSession != null){
            httpSession.invalidate();
        }

        if (uid != null){
            ONLINE_USERS.remove(uid);//相当于把下线的这个客户端删掉

            //下线通知好友
            MessageVo vo = new MessageVo(
                    uid,
                    null,
                    null,
                    LocalDateTime.now(),
                    MessageVo.Type.OFFLINE_NOTICE
            );
            service.noticeFriends(vo);
        }
    }

    /*
     * 发消息,消息内容封装成了一个vo,
     */
    public void send(MessageVo vo) throws EncodeException, IOException {
        session.getBasicRemote().sendObject(vo);
    }

    public HttpSession getHttpSession() {
        return httpSession;
    }

    public void setHttpSession(HttpSession httpSession) {
        this.httpSession = httpSession;
    }

    public Session getSession() {
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    public Long getUid() {
        return uid;
    }

    public void setUid(Long uid) {
        this.uid = uid;
    }

}
